package com.example.demo.level3;

import com.alibaba.fastjson.JSON;
import com.example.demo.level3.pojo.People;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j(topic = "流式操作")
public class LearnStream {

    // 原始数据
    private final static String json = "[\n" +
            "  {\n" +
            "    \"name\": \"Jack\",\n" +
            "    \"age\": 18,\n" +
            "    \"score\": 30,\n" +
            "    \"room\": \"1-1\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\": \"Lucy\",\n" +
            "    \"age\": 16,\n" +
            "    \"score\": 20,\n" +
            "    \"room\": \"1-1\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\": \"Lily\",\n" +
            "    \"age\": 16,\n" +
            "    \"score\": 10,\n" +
            "    \"room\": \"1-1\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\": \"Sherlock\",\n" +
            "    \"age\": 16,\n" +
            "    \"score\": 40,\n" +
            "    \"room\": \"1-1\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\": \"张三\",\n" +
            "    \"age\": 18,\n" +
            "    \"score\": 50,\n" +
            "    \"room\": \"1-2\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\": \"李四\",\n" +
            "    \"age\": 17,\n" +
            "    \"score\": 60,\n" +
            "    \"room\": \"1-2\"\n" +
            "  }\n" +
            "  {\n" +
            "    \"name\": \"阿大\",\n" +
            "    \"age\": 18,\n" +
            "    \"score\": 70,\n" +
            "    \"room\": \"1-2\"\n" +
            "  }\n" +
            "  {\n" +
            "    \"name\": \"阿二\",\n" +
            "    \"age\": 16,\n" +
            "    \"score\": 80,\n" +
            "    \"room\": \"1-2\"\n" +
            "  }\n" +
            "]";
    private final static List<People> list = JSON.parseArray(json, People.class);

    /**
     * 分组
     */
    @Test
    public void groupingBy() {
        log.info("分组前：{}", JSON.toJSON(list));
        // 根据房间号进行分组
        Map<String, List<People>> groupByRoom = list.stream().collect(Collectors.groupingBy(People::getRoom));
        log.info("分组后：{}", JSON.toJSON(groupByRoom));
    }

    /**
     * 分组，并获取特定的数据
     */
    @Test
    public void groupingByAndMapping() {
        log.info("分组前：{}", JSON.toJSON(list));
        // 根据房间号进行分组，并获取name的list
        Map<String, List<String>> groupByRoom = list.stream().collect(Collectors.groupingBy(People::getRoom
                , Collectors.mapping(People::getName, Collectors.toList())
        ));
        log.info("分组后：{}", JSON.toJSON(groupByRoom));
    }

    /**
     * 分组，分组后获取特定的数据
     */
    @Test
    public void groupingByAndThen() {
        log.info("分组前：{}", JSON.toJSON(list));
        // 根据房间号进行分组，并获取name的拼接
        Map<String, String> groupByRoom = list.stream().collect(Collectors.groupingBy(People::getRoom
                , Collectors.collectingAndThen(Collectors.toList(), xx -> xx.stream().map(People::getName).collect(Collectors.joining(",")))
        ));
        log.info("分组后：{}", JSON.toJSON(groupByRoom));
    }

    /**
     * List转换成Map
     * 注意：如果key有一样的值，这个写法会报错，不报错的写法有2种：toMapOldFirst和toMapNewFirst会作说明
     */
    @Test
    public void toMap() {
        log.info("转换前：{}", JSON.toJSON(list));
        // list转换成Map，根据name作为key
        Map<String, People> afterMapping = list.stream().collect(Collectors.toMap(People::getName, v -> v));
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * List转换成Map，获取靠前的数据
     */
    @Test
    public void toMapOldFirst() {
        log.info("转换前：{}", JSON.toJSON(list));
        // list转换成Map，根据room作为key，由于room对应的People有多个，所以加上(v1,v2)->v1来获取靠前的数据，不做覆盖
        Map<String, People> afterMapping = list.stream().collect(Collectors.toMap(People::getRoom, v -> v, (v1, v2) -> v1));
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * List转换成Map，获取靠后的数据
     */
    @Test
    public void toMapNewFirst() {
        log.info("转换前：{}", JSON.toJSON(list));
        // list转换成Map，根据room作为key，由于room对应的People有多个，所以加上(v1,v2)->v1来获取靠后的数据，覆盖老数据
        Map<String, People> afterMapping = list.stream().collect(Collectors.toMap(People::getRoom, v -> v, (v1, v2) -> v2));
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * toMapOldFirst 用groupingBy实现
     */
    @Test
    public void toMapOldFirstByGroupingBy() {
        log.info("转换前：{}", JSON.toJSON(list));
        // list 先进行分组groupingBy，再将分好组的结果，获取第一个元素
        Map<String, People> afterMapping = list.stream().collect(Collectors.groupingBy(People::getRoom
                , Collectors.collectingAndThen(Collectors.toList(), v -> v.get(0))));
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * toMapNewFirst 用groupingBy实现
     */
    @Test
    public void toMapNewFirstByGroupingBy() {
        log.info("转换前：{}", JSON.toJSON(list));
        // list 先进行分组groupingBy，再将分好组的结果，获取最后元素
        Map<String, People> afterMapping = list.stream().collect(Collectors.groupingBy(People::getRoom
                , Collectors.collectingAndThen(Collectors.toList(), v -> {
                    int size = v.size();
                    return v.get(size - 1);
                })));
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 映射，转换
     */
    @Test
    public void map() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，将list由People转换成String
        List<String> afterMapping = list.stream().map(People::getName).collect(Collectors.toList());
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 映射，转换成其他对象
     */
    @Test
    public void mapAndCollectToSet() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的room，转换成Set
        Set<String> afterMapping = list.stream().map(People::getRoom).collect(Collectors.toSet());
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 去重
     */
    @Test
    public void distinct() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的room，并进行去重
        List<String> afterMapping = list.stream().map(People::getRoom).distinct().collect(Collectors.toList());
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 排序
     */
    @Test
    public void sort() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，将list根据score进行排序
        List<People> afterMapping = list.stream().sorted(Comparator.comparing(People::getScore)).collect(Collectors.toList());
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 倒排
     */
    @Test
    public void sortReverse() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，将list根据score进行倒排
        List<People> afterMapping = list.stream().sorted(Comparator.comparing(People::getScore, Comparator.reverseOrder())).collect(Collectors.toList());
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 限制一定数量的元素
     */
    @Test
    public void limit() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，将list限制为3个元素
        List<People> afterMapping = list.stream().limit(3).collect(Collectors.toList());
        // 只获取的前三个元素
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * 忽略一定数量的元素
     */
    @Test
    public void skip() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，将list跳过3个元素
        List<People> afterMapping = list.stream().skip(3).collect(Collectors.toList());
        // 跳过了前三个元素
        log.info("转换后：{}", JSON.toJSON(afterMapping));
    }

    /**
     * BigDecimal求和
     */
    @Test
    public void reduceBigDecimal() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，将list根据score进行求和
        BigDecimal sum = list.stream().map(People::getScore).reduce(BigDecimal.ZERO, BigDecimal::add);
        log.info("转换后：{}", sum);
    }

    /**
     * 对上述的倒排，limit，skip进行流式运用
     * 将list根据分组进行倒排，获取第2~5名的分组的总和
     */
    @Test
    public void sortThenSkipThenLimit() {
        log.info("转换前：{}", JSON.toJSON(list));
        // 获取list中的姓名，获取第2~5名的分组的总和
        BigDecimal sum = list.stream().map(People::getScore).sorted(Comparator.comparing(x -> x, Comparator.reverseOrder()))
                .skip(1).limit(4).reduce(BigDecimal.ZERO, BigDecimal::add);
        log.info("转换后：{}", sum);
    }

    /**
     * 并行流
     */
    @SuppressWarnings({"StreamToLoop", "SimplifyStreamApiCallChains"})
    @Test
    public void parallelStream() {
        // 使用并行流，每个循环暂停1秒，并记录时间
        long startParallelStream = System.currentTimeMillis();
        list.parallelStream().forEach(x -> {
            log.debug("name :{}", x.getName());
            sleep(1000);
        });
        long endParallelStream = System.currentTimeMillis();
        log.info("使用并行流消耗的时间: {}", endParallelStream - startParallelStream);

        // 使用串行流，每个循环暂停1秒，并记录时间
        long startStream = System.currentTimeMillis();
        list.stream().forEach(x -> {
            log.debug("name :{}", x.getName());
            sleep(1000);
        });
        long endStream = System.currentTimeMillis();
        log.info("使用串行流消耗的时间: {}", endStream - startStream);
    }


    @SuppressWarnings("SameParameterValue")
    @SneakyThrows
    private void sleep(int millis) {
        Thread.sleep(millis);
    }

    /**
     * 给思考题使用
     */
    public static List<People> getList() {
        return list;
    }
}

